package net.p2pcdn.controller;

import com.paypal.api.payments.Event;
import com.paypal.api.payments.Links;
import com.paypal.api.payments.Payment;
import com.paypal.base.Constants;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;
import com.qc.bean.result.ResponseData;
import net.p2pcdn.common.PayPalAccessTokenProvider;
import net.p2pcdn.core.product.domain.ProductType;
import net.p2pcdn.pay.*;
import net.p2pcdn.pay.offline.BalancePaymentService;
import net.p2pcdn.pay.paypal.config.CreatePaymentCommand;
import net.p2pcdn.pay.paypal.config.PaypalPaymentIntent;
import net.p2pcdn.pay.paypal.config.PaypalPaymentMethod;
import net.p2pcdn.pay.paypal.service.PaypalService;
import net.p2pcdn.user.domain.User;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zx
 */
@RestController
@RequestMapping("/pay")
public class PaymentController {
    public static final String PAYPAL_SUCCESS_URL = "success";
    public static final String PAYPAL_CANCEL_URL = "cancel";
    private final Logger log = LoggerFactory.getLogger(getClass());
    @Autowired
    private PaypalService paypalService;
    @Autowired
    private PayPalAccessTokenProvider payPalAccessTokenProvider;
    @Autowired
    private OrderGeneratorFactory orderGeneratorFactory;
    @Autowired
    private APIContext apiContext;
    @Value("${pay.success.url}")
    private String successUrl;
    @Value("${pay.cancel.url}")
    private String cancelUrl;
    @Value("${pay.notify.url}")
    private String notifyUrl;
    @Autowired
    private WebhookCreator webhookCreator;
    @Autowired
    private OrderProcessorFactory orderProcessorFactory;
    @Autowired
    private BalancePaymentService balancePaymentService;

    /**
     * for test
     *
     * @param request
     * @return
     * @throws PayPalRESTException
     */
    @PostMapping(value = "prepare")
    public ResponseData createPayment(HttpServletRequest request) throws PayPalRESTException {
        String cancelUrl = URLUtils.getBaseURl(request) + this.cancelUrl;
        String successUrl = URLUtils.getBaseURl(request) + this.successUrl;
        String notifyUrl = URLUtils.getBaseURl(request) + this.notifyUrl;
        CreatePaymentCommand createPaymentCommand = new CreatePaymentCommand();
        createPaymentCommand.setUserId(6)
                .setProductType(ProductType.PACKAGE)
                .setItemId(1)
                .setQuantity(1)
                .setDomains("wwww.baidu.com,a.qker.com,c.qker.com")
                .setCurrency("USD")
                .setPaymentMethod(PaypalPaymentMethod.paypal)
                .setPaypalPaymentIntent(PaypalPaymentIntent.sale)
                .setNotifyUrl(notifyUrl)
                .setCancelUrl(cancelUrl)
                .setSuccessUrl(successUrl);
        Payment payment = orderGeneratorFactory.createPayment(createPaymentCommand);
        for (Links links : payment.getLinks()) {
            if (links.getRel().equals("approval_url")) {
                return ResponseData.success(links.getHref());
            }
        }
        return ResponseData.error(payment.getFailureReason());
    }

    /**
     * @param request
     * @return
     * @throws PayPalRESTException
     */
    @PostMapping(value = "chargeBalance")
    public ResponseData chargeBalance(HttpServletRequest request, String amountOfMoney) throws PayPalRESTException {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        if (user == null) {
            return ResponseData.error("您会登录或会话已过期", 403);
        }
        String cancelUrl = URLUtils.getBaseURl(request) + this.cancelUrl;
        String successUrl = URLUtils.getBaseURl(request) + this.successUrl;
        String notifyUrl = URLUtils.getBaseURl(request) + this.notifyUrl;
        CreatePaymentCommand createPaymentCommand = new CreatePaymentCommand();
        createPaymentCommand.setUserId(user.getId())
                .setProductType(ProductType.BALANCE)
                .setItemId(1)
                .setAmountOfMoney(amountOfMoney)
                .setCurrency("USD")
                .setPaymentMethod(PaypalPaymentMethod.paypal)
                .setPaypalPaymentIntent(PaypalPaymentIntent.sale)
                .setNotifyUrl(notifyUrl)
                .setCancelUrl(cancelUrl)
                .setSuccessUrl(successUrl);
        Payment payment = orderGeneratorFactory.createPayment(createPaymentCommand);
        for (Links links : payment.getLinks()) {
            if (links.getRel().equals("approval_url")) {
                return ResponseData.success(links.getHref());
            }
        }
        return ResponseData.error(payment.getFailureReason());
    }

    /**
     * @param request
     * @return
     * @throws PayPalRESTException
     */
    @PostMapping(value = "product")
    public ResponseData purchaseProduct(HttpServletRequest request, int productId, String domains, int quantity) throws PayPalRESTException {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        if (user == null) {
            return ResponseData.error("您会登录或会话已过期", 403);
        }
        String cancelUrl = URLUtils.getBaseURl(request) + this.cancelUrl;
        String successUrl = URLUtils.getBaseURl(request) + this.successUrl;
        String notifyUrl = URLUtils.getBaseURl(request) + this.notifyUrl;
        CreatePaymentCommand createPaymentCommand = new CreatePaymentCommand();
        createPaymentCommand.setUserId(user.getId())
                .setProductType(ProductType.PACKAGE)
                .setItemId(productId)
                .setQuantity(quantity)
                .setDomains(domains)
                .setCurrency("USD")
                .setPaymentMethod(PaypalPaymentMethod.paypal)
                .setPaypalPaymentIntent(PaypalPaymentIntent.sale)
                .setNotifyUrl(notifyUrl)
                .setCancelUrl(cancelUrl)
                .setSuccessUrl(successUrl);
        Payment payment = orderGeneratorFactory.createPayment(createPaymentCommand);
        for (Links links : payment.getLinks()) {
            if (links.getRel().equals("approval_url")) {
                return ResponseData.success(links.getHref());
            }
        }
        return ResponseData.error(payment.getFailureReason());
    }

    /**
     * 余额购买套餐
     *
     * @return
     */
    @PostMapping(value = "balance/product")
    public ResponseData purchaseProduct(int productId, String domains, int quantity) {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        if (user == null) {
            return ResponseData.error("您会登录或会话已过期", 403);
        }
        return balancePaymentService.purchaseTrafficPackage(user.getId(), productId, quantity, domains);
    }


    @PostMapping(value = "balance/traffic")
    public ResponseData purchaseProduct(int productId, int contractId, int quantity) {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        if (user == null) {
            return ResponseData.error("您会登录或会话已过期", 403);
        }
        return balancePaymentService.purchaseBags(user.getId(), contractId, productId, quantity);
    }

    /**
     * @param request
     * @return
     * @throws PayPalRESTException
     */
    @PostMapping(value = "traffic")
    public ResponseData purchaseTraffic(HttpServletRequest request, int contractId, int productId, int quantity) throws PayPalRESTException {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        if (user == null) {
            return ResponseData.error("您会登录或会话已过期", 403);
        }
        String cancelUrl = URLUtils.getBaseURl(request) + this.cancelUrl;
        String successUrl = URLUtils.getBaseURl(request) + this.successUrl;
        String notifyUrl = URLUtils.getBaseURl(request) + this.notifyUrl;
        CreatePaymentCommand createPaymentCommand = new CreatePaymentCommand();
        createPaymentCommand.setUserId(user.getId())
                .setProductType(ProductType.PACKAGE)
                .setItemId(productId)
                .setQuantity(quantity)
                .setContractId(contractId)
                .setCurrency("USD")
                .setPaymentMethod(PaypalPaymentMethod.paypal)
                .setPaypalPaymentIntent(PaypalPaymentIntent.sale)
                .setNotifyUrl(notifyUrl)
                .setCancelUrl(cancelUrl)
                .setSuccessUrl(successUrl);
        Payment payment = orderGeneratorFactory.createPayment(createPaymentCommand);
        for (Links links : payment.getLinks()) {
            if (links.getRel().equals("approval_url")) {
                return ResponseData.success(links.getHref());
            }
        }
        return ResponseData.error(payment.getFailureReason());
    }

    @PostMapping(value = PAYPAL_CANCEL_URL)
    public ResponseData cancelPay(String tradeNo) {
        orderProcessorFactory.cancelOrder(tradeNo);
        return ResponseData.success("ok");
    }
    
    @GetMapping(value = PAYPAL_SUCCESS_URL)
    public ResponseData successPay(String paymentId, String PayerID, String tradeNo) {
        try {
            Payment payment = paypalService.executePayment(paymentId, PayerID);
            if (payment.getState().equals("approved")) {
                orderProcessorFactory.process(paymentId, PayerID, tradeNo);
                return ResponseData.success("ok");
            }
        } catch (PayPalRESTException e) {
            log.error(e.getMessage());
        }
        return ResponseData.error("未付款", 500);
    }

    @GetMapping(value = "token")
    public ResponseData getToken() throws IOException {
        return ResponseData.success(payPalAccessTokenProvider.getAccessToken());
    }


    @GetMapping(value = "notify")
    public void validateNotify(HttpServletRequest request, HttpServletResponse response) {
        boolean valid = validateEvent(request, response);
        if (valid) {
            log.info("=========================================================");
            log.info("request is illegal");
            log.info("=========================================================");
        } else {
            log.info("=========================================================");
            log.error("request is faker request");
            log.info("=========================================================");
        }
    }


    @PostMapping(value = "notify")
    public void handleNotify(HttpServletRequest request, HttpServletResponse response) {
        apiContext.addConfiguration(Constants.PAYPAL_WEBHOOK_ID, WebhookConfig.WEB_HOOK_ID);
        String body = getBody(request);
        log.info("Webhook body:  " + body);
        Boolean result = null;
        try {
            result = Event.validateReceivedEvent(apiContext, getHeadersInfo(request), body);
            log.info("Webhook Validated:  " + result);
        } catch (PayPalRESTException | InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
            log.error(e.getMessage(), e);
        }
    }

    private boolean validateEvent(HttpServletRequest request, HttpServletResponse response) {
        apiContext.addConfiguration(Constants.PAYPAL_WEBHOOK_ID, WebhookConfig.WEB_HOOK_ID);
        String body = getBody(request);
        try {
            log.info("Webhook body:  " + body);
            return Event.validateReceivedEvent(apiContext, getHeadersInfo(request), body);
        } catch (PayPalRESTException | InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
            log.error(e.getMessage(), e);
        }
        return false;
    }

    private static Map<String, String> getHeadersInfo(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        @SuppressWarnings("rawtypes")
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }

    private String getBody(HttpServletRequest request) {
        String body;
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        try {
            InputStream inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[256];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            }
        } catch (IOException ex) {
            log.error(ex.getMessage(), ex);
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ex) {
                    log.error(ex.getMessage(), ex);
                }
            }
        }
        body = stringBuilder.toString();
        return body;
    }

    @GetMapping(value = "webhook")
    public ResponseData validateNotify() {
        webhookCreator.createWebhook();
        return ResponseData.success("ok");
    }
}